/**
 * IndexedDB 工具类
 * 提供通用的数据库操作方法，支持多数据库、多存储对象、事务管理
 * @version 1.0.0
 */

// 导出工具类，供其他模块使用
export { IndexDbManager };

// 定义接口和类型
interface DBConfig {
  name: string; // 数据库名称
  version: number; // 数据库版本
  stores: StoreConfig[]; // 存储对象配置
}

interface StoreConfig {
  name: string; // 存储对象名称
  keyPath: string; // 主键
  indexes?: IndexConfig[]; // 索引配置
  autoIncrement?: boolean; // 自动递增
}

interface IndexConfig {
  name: string; // 索引名称
  keyPath: string | string[]; // 索引键路径
  options?: IDBIndexParameters; // 索引选项
}

interface UpdateProperties {
  id: IDBValidKey; // 主键
  properties: Record<string, any>; // 要更新的属性
}

type TransactionType = 'readonly' | 'readwrite';

class IndexDbManager {
  private dbName: string;
  private version: number;
  private db: IDBDatabase | null = null;
  private storeConfigs: Map<string, StoreConfig> = new Map();

  /**
   * 构造函数
   * @param config 数据库配置
   */
  constructor(config: DBConfig) {
    this.dbName = config.name;
    this.version = config.version;

    // 存储所有存储对象配置
    config.stores.forEach((store) => {
      this.storeConfigs.set(store.name, store);
    });
  }

  /**
   * 初始化数据库连接
   * @returns 返回 Promise，连接成功时 resolve
   */
  async connect(): Promise<boolean> {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, this.version);

      // 处理版本更新
      request.onupgradeneeded = (event) => {
        const db = (event.target as IDBOpenDBRequest).result;

        // 创建或更新存储对象
        this.storeConfigs.forEach((storeConfig) => {
          // 如果存储对象已存在则删除
          if (db.objectStoreNames.contains(storeConfig.name)) {
            db.deleteObjectStore(storeConfig.name);
          }

          // 创建新的存储对象
          const store: IDBObjectStore = db.createObjectStore(storeConfig.name, {
            keyPath: storeConfig.keyPath,
            autoIncrement: storeConfig.autoIncrement || false,
          });

          // 创建索引
          if (storeConfig.indexes) {
            storeConfig.indexes.forEach((indexConfig) => {
              store.createIndex(
                indexConfig.name,
                indexConfig.keyPath,
                indexConfig.options,
              );
            });
          }
        });
      };

      // 连接成功
      request.onsuccess = (event) => {
        this.db = (event.target as IDBOpenDBRequest).result;

        // 监听连接关闭事件
        this.db.onclose = () => {
          this.db = null;
          console.log(`数据库 ${this.dbName} 连接已关闭`);
        };

        console.log(`数据库 ${this.dbName} 连接成功`);
        resolve(true);
      };

      // 连接失败
      request.onerror = (event) => {
        console.error(
          `数据库 ${this.dbName} 连接失败:`,
          (event.target as IDBOpenDBRequest).error,
        );
        reject((event.target as IDBOpenDBRequest).error);
      };
    });
  }

  /**
   * 关闭数据库连接
   */
  close(): void {
    if (this.db) {
      this.db.close();
      this.db = null;
      console.log(`数据库 ${this.dbName} 已关闭`);
    }
  }

  /**
   * 添加数据
   * @param storeName 存储对象名称
   * @param data 要添加的数据，可以是单个对象或对象数组
   * @returns 返回 Promise，添加成功时 resolve
   */
  async add<T>(
    storeName: string,
    data: T | T[],
  ): Promise<IDBValidKey | IDBValidKey[] | null> {
    return this.runTransaction(storeName, 'readwrite', async (store) => {
      const errors: any = [];
      if (Array.isArray(data)) {
        // 批量添加
        const keys: IDBValidKey[] = [];
        for (const item of data) {
          let key;
          try {
            const exists = await this.promisifyRequest(
              store.get(
                store.keyPath
                  ? (item as any)[store.keyPath as string]
                  : undefined,
              ),
            );
            if (exists !== undefined) {
              // 已存在则跳过或返回 null
              key = null;
              continue;
            }
            key = await this.promisifyRequest<IDBValidKey>(store.add(item));
          } catch (err) {
            errors.push([item, err]);
            key = null; // 确保在异常情况下也返回 null
          }
          if (key !== null) {
            keys.push(key);
          }
        }
        if (errors.length > 0) {
          console.error(
            `批量添加数据时有 ${errors.length} 条数据添加失败`,
            errors,
          );
        }
        return keys;
      } else {
        // 添加单个
        return await this.promisifyRequest<IDBValidKey>(store.add(data)).catch(
          () => {
            // 如果添加失败，返回 null
            console.error(`添加数据失败:`, data);
            // return Promise.resolve(undefined as any);
            return null;
          },
        );
      }
    });
  }

  /**
   * 更新数据
   * @param storeName 存储对象名称
   * @param data 要更新的数据，可以是单个对象或对象数组
   * @returns 返回 Promise，更新成功时 resolve
   */
  async put<T>(
    storeName: string,
    data: T | T[],
  ): Promise<IDBValidKey | IDBValidKey[]> {
    return this.runTransaction(storeName, 'readwrite', async (store) => {
      if (Array.isArray(data)) {
        // 批量更新
        const keys: IDBValidKey[] = [];
        for (const item of data) {
          const key = await this.promisifyRequest<IDBValidKey>(store.put(item));
          keys.push(key);
        }
        return keys;
      } else {
        // 更新单个
        return await this.promisifyRequest<IDBValidKey>(store.put(data));
      }
    });
  }

  /**
   * 此处在纠结， 是要封装一个更通用的方法， 还是直接写一个略带业务字段(properties)的方法
   * 思考结果， 带点业务咋啦？ 不能有代码洁癖， 这里从实际来看， 只会更新 `properties` 这唯一的字段
   */
  async updateProperties(
    storeName: string,
    data: UpdateProperties | UpdateProperties[],
  ): Promise<IDBValidKey | IDBValidKey[]> {
    const mergeAndUpdate = async (
      store: IDBObjectStore,
      obj: UpdateProperties,
    ) => {
      const keyPath = store.keyPath as string;
      const key = (obj as any)[keyPath] as IDBValidKey;
      const existData = (await this.promisifyRequest(store.get(key))) as Record<
        string,
        any
      >;
      if (existData) {
        // 如果数据已存在，则合并数据

        existData.properties = Object.assign(
          existData.properties,
          obj.properties,
        );
        return this.promisifyRequest<IDBValidKey>(store.put(existData));
      } else {
        throw new Error(`数据不存在，无法更新: ${key}`);
      }
    };
    return this.runTransaction(storeName, 'readwrite', async (store) => {
      const keys: IDBValidKey[] = [];
      if (Array.isArray(data)) {
        // 批量更新
        for (const item of data) {
          const key = await mergeAndUpdate(store, item);
          keys.push(key);
        }
        return keys;
      } else {
        // 更新单个
        const key = await mergeAndUpdate(store, data);
        keys.push(key);
        return keys;
      }
    });
  }

  /**
   * 删除数据
   * @param storeName 存储对象名称
   * @param key 要删除的数据的主键
   * @returns 返回 Promise，删除成功时 resolve
   */
  async delete(
    storeName: string,
    key: IDBValidKey | IDBKeyRange,
  ): Promise<void> {
    return this.runTransaction(storeName, 'readwrite', async (store) => {
      await this.promisifyRequest(store.delete(key));
    });
  }

  /**
   * 清空存储对象中的所有数据
   * @param storeName 存储对象名称
   * @returns 返回 Promise，清空成功时 resolve
   */
  async clear(storeName: string): Promise<void> {
    return this.runTransaction(storeName, 'readwrite', async (store) => {
      await this.promisifyRequest(store.clear());
    });
  }

  /**
   * 根据主键获取数据
   * @param storeName 存储对象名称
   * @param key 主键值
   * @returns 返回 Promise，查询成功时 resolve 结果数据
   */
  async get<T>(
    storeName: string,
    key: IDBValidKey | IDBKeyRange,
  ): Promise<T | undefined> {
    return this.runTransaction(storeName, 'readonly', async (store) => {
      return await this.promisifyRequest<T>(store.get(key));
    });
  }

  /**
   * 获取所有数据
   * @param storeName 存储对象名称
   * @returns 返回 Promise，查询成功时 resolve 结果数据数组
   */
  async getAll<T>(storeName: string): Promise<T[]> {
    return this.runTransaction(storeName, 'readonly', async (store) => {
      return await this.promisifyRequest<T[]>(store.getAll());
    });
  }

  /**
   * 根据索引查询数据
   * @param storeName 存储对象名称
   * @param indexName 索引名称
   * @param value 索引值
   * @returns 返回 Promise，查询成功时 resolve 结果数据数组
   */
  async getByIndex<T>(
    storeName: string,
    indexName: string,
    value: any,
  ): Promise<T[]> {
    return this.runTransaction(storeName, 'readonly', async (store) => {
      const index = store.index(indexName);
      return await this.promisifyRequest<T[]>(index.getAll(value));
    });
  }

  /**
   * 按范围查询数据
   * @param storeName 存储对象名称
   * @param range 范围对象
   * @param indexName 可选，索引名称
   * @returns 返回 Promise，查询成功时 resolve 结果数据数组
   */
  async getByRange<T>(
    storeName: string,
    range: IDBKeyRange,
    indexName?: string,
  ): Promise<T[]> {
    return this.runTransaction(storeName, 'readonly', async (store) => {
      const target = indexName ? store.index(indexName) : store;
      return await this.promisifyRequest<T[]>(target.getAll(range));
    });
  }

  /**
   * 高级查询：使用游标遍历数据
   * @param storeName 存储对象名称
   * @param callback 回调函数，处理每条数据
   * @param query 可选，查询条件
   * @param indexName 可选，索引名称
   * @param direction 可选，遍历方向
   * @returns 返回 Promise，查询成功时 resolve 结果数据数组
   */
  async query<T>(
    storeName: string,
    callback: (item: T, cursor: IDBCursorWithValue) => boolean | void,
    query?: IDBValidKey | IDBKeyRange,
    indexName?: string,
    direction?: IDBCursorDirection,
  ): Promise<T[]> {
    return this.runTransaction(storeName, 'readonly', async (store) => {
      const results: T[] = [];
      const target = indexName ? store.index(indexName) : store;

      return new Promise((resolve, reject) => {
        const request = target.openCursor(query, direction);

        request.onsuccess = (event) => {
          const cursor = (event.target as IDBRequest)
            .result as IDBCursorWithValue;

          if (cursor) {
            // 调用回调处理数据
            const result = callback(cursor.value as T, cursor);
            results.push(cursor.value as T);

            // 如果回调返回 false，则停止遍历
            if (result !== false) {
              cursor.continue();
            } else {
              resolve(results);
            }
          } else {
            resolve(results);
          }
        };

        request.onerror = () => {
          reject(request.error);
        };
      });
    });
  }

  /**
   * 获取存储对象中的数据数量
   * @param storeName 存储对象名称
   * @returns 返回 Promise，查询成功时 resolve 数据数量
   */
  async count(storeName: string): Promise<number> {
    return this.runTransaction(storeName, 'readonly', async (store) => {
      return await this.promisifyRequest<number>(store.count());
    });
  }

  /**
   * 执行事务
   * @param storeNames 存储对象名称或名称数组
   * @param mode 事务模式
   * @param callback 回调函数
   * @returns 返回 Promise，事务成功时 resolve 回调结果
   */
  private async runTransaction<T>(
    storeNames: string | string[],
    mode: TransactionType,
    callback: (store: IDBObjectStore) => Promise<T>,
  ): Promise<T> {
    if (!this.db) {
      throw new Error('数据库未连接，请先调用 connect() 方法');
    }

    return new Promise<T>((resolve, reject) => {
      const transaction = this.db!.transaction(storeNames, mode);
      const store = transaction.objectStore(
        Array.isArray(storeNames) ? storeNames[0] : storeNames,
      );

      transaction.oncomplete = () => {
        // 事务完成
      };

      transaction.onerror = (event) => {
        reject((event.target as IDBTransaction).error);
      };

      // 执行回调
      callback(store).then(resolve).catch(reject);
    });
  }

  /**
   * 将 IDBRequest 转换为 Promise
   * @param request IDBRequest 对象
   * @returns 返回 Promise
   */
  private promisifyRequest<T>(request: IDBRequest): Promise<T> {
    return new Promise<T>((resolve, reject) => {
      request.onsuccess = () => resolve(request.result as T);
      request.onerror = () => reject(request.error);
    });
  }

  /**
   * 创建范围查询条件
   * 静态工具方法，提供常见的范围查询条件创建
   */
  static createRange = {
    /**
     * 精确匹配
     */
    only: (value: any): IDBKeyRange => IDBKeyRange.only(value),

    /**
     * 下边界（包含）
     */
    lowerBound: (lower: any, open = false): IDBKeyRange =>
      IDBKeyRange.lowerBound(lower, open),

    /**
     * 上边界（包含）
     */
    upperBound: (upper: any, open = false): IDBKeyRange =>
      IDBKeyRange.upperBound(upper, open),

    /**
     * 范围（包含下边界和上边界）
     */
    bound: (
      lower: any,
      upper: any,
      lowerOpen = false,
      upperOpen = false,
    ): IDBKeyRange => IDBKeyRange.bound(lower, upper, lowerOpen, upperOpen),
  };

  /**
   * 删除数据库
   * @param dbName 数据库名称
   * @returns 返回 Promise，删除成功时 resolve
   */
  static deleteDatabase(dbName: string): Promise<void> {
    return new Promise((resolve, reject) => {
      const request = indexedDB.deleteDatabase(dbName);
      request.onsuccess = () => {
        console.log(`数据库 ${dbName} 删除成功`);
        resolve();
      };
      request.onerror = (event) => {
        console.error(
          `删除数据库 ${dbName} 失败:`,
          (event.target as IDBOpenDBRequest).error,
        );
        reject((event.target as IDBOpenDBRequest).error);
      };
    });
  }
}
